package br.com.pmz.utilidades.conexao.hibernate;

import java.util.Collection;

import org.hibernate.Criteria;
import org.hibernate.Transaction;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Order;

import br.com.pmz.utilidades.util.ConstantesHibernate;
import br.com.pmz.utilidades.util.Util;

/**
 * Classe que cont&eacute;m a gera&ccedil;&atilde;oo de querys por criteria.
 *
 * @author Pedro Manoel Zanin
 * @param <T> Classe que ser&aacute; utilizada no criteria
 */
public class BaseHibernate < T > extends HibernateSession {

    /**
     * Collection que cont&eacute;m os filtros da query.
     */
    protected Collection < Criterion > where;

    /**
     * Collection que cont&eacute;m as ordena&ccedil;&otilde;es da query.
     */
    protected Collection < String > order;

    /**
     * Construtor padr&atilde;o chamando o super para criar a instancia.
     */
    public BaseHibernate() {
        super();
    }

    /**
     * Retorna uma lista de objetos filtrado atrav&eacute;s dos parametros
     * passados. Obrigatoriamente deve-se passar uma ordena&ccedil;&atilde;o. A
     * pagina&ccedil;&atilde;o &eacute; implementada atrav&eacute;s da
     * sess&atilde;o do usu&aacute;rio.
     *
     * @param clazz
     *            Classe que ser&aacute; utilizada para criar a query.
     * @param whereP
     *            Collection que cont&eacute;m os filtros da query.
     * @param orders
     *            Collection que cont&eacute;m a ordena&ccedil;&atilde;o das
     *            querys.
     * @param asc
     *            Define se a ordena&ccedil;&atilde;o &eacute; crescente ou
     *            decrescente, por padr&atilde;o ser&aacute; crescente.
     * @return Lista contendo objetos retornados do banco.
     */
    @SuppressWarnings(value = "unchecked")
    protected Collection < T > getList(final Class < T > clazz,
            final Collection < Criterion > whereP,
            final Collection < String > orders, final Boolean asc) {
        Criteria criteria = createCriteria(clazz, where, orders, asc);
        criteria.setMaxResults(ConstantesHibernate.NUMBER_MAX.getValue());
        criteria.setFirstResult(0);
        Collection < T > col = criteria.list();
        return col;
    }

    /**
     * M&eacute;todo que inicia o objeto criteria e adiciona as clausulas orders
     * geradas anteriormente e adicionadas na collection.
     *
     * @param clazz
     *            Classe do objeto mapeado que ser&aacute; utilizado na
     *            criteria.
     * @param whereP
     *            Collection com clausulas para cria&ccedil;&atilde;o da query
     * @param orders
     *            collection com as condi&ccedil;&otilde;es utilizadas na query
     *            para gerar o order by
     * @param asc
     *            True para crescente e false para decrescente
     * @return Objeto criteria que ir&aacute; gerar um query
     */
    private Criteria createCriteria(final Class < T > clazz,
            final Collection < Criterion > whereP,
            final Collection < String > orders, final Boolean asc) {
        Criteria criteria = session.createCriteria(clazz);
        addWhere(criteria, whereP);
        if (Util.notEmpty(orders)) {
            addOrder(criteria, orders, asc);
        }
        return criteria;
    }

    /**
     * Monta o filtro da query.
     *
     * @param criteria
     *            Objeto que monta a query.
     * @param whereP
     *            Collection que cont&eacute;m todos os filtros.
     */
    private void addWhere(final Criteria criteria,
            final Collection < Criterion > whereP) {
        for (Criterion t : whereP) {
            criteria.add(t);
        }
    }

    /**
     * Monta a ordena&ccedil;&atilde;o da query.
     *
     * @param criteria
     *            Objeto que monta a query.
     * @param orders
     *            Collection que cont&eacute;m a ordena&ccedil;&atilde;o.
     * @param asc
     *            Determina se a ordena&ccedil;&atilde;o ser&aacute; crescente
     *            ou decrescente.
     */
    private void addOrder(final Criteria criteria,
            final Collection < String > orders, final Boolean asc) {
        if (asc) {
            for (String aOrder : orders) {
                criteria.addOrder(Order.asc(aOrder));
            }
        } else {
            for (String aOrder : orders) {
                criteria.addOrder(Order.desc(aOrder));
            }
        }
    }

    /**
     * Retorna uma lista de objetos filtrado atrav&eacute;s dos parametros
     * passados. Obrigatoriamente deve-se passar uma ordena&ccedil;&atilde;o. A
     * pagina&ccedil;&atilde;o &eacute; implementada atrav&eacute;s da
     * sess&atilde;o do usu&aacute;rio.
     *
     * @param clazz
     *            Classe que ser&aacute; utilizada para criar a query.
     * @param whereP
     *            Collection que cont&eacute;m os filtros da query.
     * @param orders
     *            Collection que cont&eacute;m a ordena&ccedil;&atilde;o das
     *            querys.
     * @return Lista contendo objetos retornados do banco.
     */
    protected Collection < T > getList(final Class < T > clazz,
            final Collection < Criterion > whereP,
            final Collection < String > orders) {
        return getList(clazz, whereP, orders, Boolean.TRUE);
    }

    /**
     * Retorna uma lista de objetos filtrado atrav&eacute;s dos parametros
     * passados. Obrigatoriamente deve-se passar uma ordena&ccedil;&atilde;o. A
     * pagina&ccedil;&atilde;o &eacute; implementada atrav&eacute;s da
     * sess&atilde;o do usu&aacute;rio.
     *
     * @param clazz
     *            Classe que ser&aacute; utilizada para criar a query.
     * @param whereP
     *            Collection que cont&eacute;m os filtros da query. ser&aacute;
     *            crescente.
     * @return Lista contendo objetos retornados do banco.
     */
    protected Collection < T > getList(final Class < T > clazz,
            final Collection < Criterion > whereP) {
        return getList(clazz, whereP, null, Boolean.TRUE);
    }

    /**
     * Retorna um objeto unico filtrando pelas chaves prim&aacute;rias.
     *
     * @param clazz
     *            Tipo de objeto que ser&aacute; retornado.
     * @param whereP
     *            Chaves primarias
     * @return Objeto carregado do banco de dados.
     */
    @SuppressWarnings(value = "unchecked")
    protected T getObjectById(final Class < T > clazz,
            final Collection < Criterion > whereP) {
        Criteria criteria = createCriteria(clazz, whereP, null, Boolean.FALSE);
        return (T) criteria.uniqueResult();
    }

    /**
     * M&eacute;todo gen&eacute;rico que salva os dados no banco de dados,
     * quando n&atilde;o existe ele insere e quando existe ele faz update.
     *
     * @param vo
     *            Objeto a ser salvo.
     */
    protected void saveOrUpdate(final T vo) {
        Transaction tra = session.getTransaction();
        tra.begin();
        session.saveOrUpdate(vo);
        tra.commit();
    }
}
